/******************************************************************************
*       UART单线键盘接口驱动库
*
* 程序说明：BC6xxx和BC759x芯片键盘接口的驱动函数库
* 版权所有:	北京凌志比高科技有限公司 https://bitcode.com.cn
* 版本号: V1.2
* 初始版本：2021年3月10日
* 版本历史:
*   2021年3月   V1.0
*   2021年3月   V1.1  增加了对长时间无按键的检测支持
*   2021年3月   V1.2  改进执行效率
* 使用方法：
*   将此头文件key_scan.h加入所有需要使用本驱动库中函数的c源文件，并将本文件加入项目源文件列表，
*   即可从用户程序中直接调用本库中函数。
*	通常可以在串口中断中呼叫update_key_status()函数，然后从主程序循环中查询 is_key_changed()
*	函数，如果结果不为零，则呼叫get_key_value()函数获取键值，根据键值执行相应键盘响应操作即可。
*	详细使用方法，参见《UART单线键盘接口驱动库技术说明书》
******************************************************************************/

#include "key_scan.h"

#ifndef NULL
#define NULL 0
#endif

struct keyboard_t
{
    unsigned char         NewKeyAvailable;
    unsigned char         DetectKeyRelease;
    unsigned char         LastKeyEvent;
    unsigned char         ReportedKey;
    unsigned char         CombinedKeyCount;
    unsigned char         LongPressKeyCount;
    unsigned int          LongPressCount;
    unsigned int          TimeCounter;
    const unsigned char** pCombinedKeys;
    const unsigned char** pLongPressKeys;
    unsigned char*        pCombinedKeyStatusArray;
    void (*pCallback)(unsigned char);
} Keyboard =        // 定义键盘接口数据结构并设置默认值。此处设置为不检测按键释放，长按键时长为2000次计数
    {
        .LongPressCount = 2000
    };

// 各种键数下对应的位
const unsigned char FullBits[8] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f };

/******************************************************************************
* 查询是否有新按键
* 根据工作模式的设置，返回是否当前有未读取的新按键。只有符合当前工作模式设置的键盘变化才会返回，
* 比如设置了不检测按键的释放，则按键释放不会改变这个查询的结果。
* 返回值: 如果没有新按键返回0,其它值表示有新按键
******************************************************************************/
unsigned char is_key_changed(void)
{
    return Keyboard.NewKeyAvailable;
}

/******************************************************************************
* 设置键盘检测模式
* 决定是否检测按键(包括组合键)的释放。
* 参数: Mode  --  工作模式， 0=只检测按键按下(导通), 1=同时检测按键按下和释放
******************************************************************************/
void set_detect_mode(unsigned char Mode)
{
    Keyboard.DetectKeyRelease = Mode;
}

/******************************************************************************
* 设置长按键计数值
* 长按键的时间由此计数值决定。用户定期呼叫 long_press_tick() 函数，每次呼叫内部计数器被
* 加一，而每次键盘活动则使计数器清零。当内部计数器的值大于此值时，即触发一个长按键检测，如果
* 此前最后一次按键(包括组合键)属于需检测长按键的键值，则报告一个长按键(以用户设定的特殊键值
* 报告一个新按键)
* 参数: CountLimit  --  最大值65534, 达到触发条件所需的计数值，默认值为 2000
******************************************************************************/
void set_longpress_count(unsigned int CountLimit)
{
    Keyboard.LongPressCount = CountLimit;
}

/******************************************************************************
* 设置回调函数
* 一般应用时在串口中断中呼叫更新键盘状态函数，而实际的键盘处理程序则放在主循环中。因为处理键盘
* 事件一般不是高优先级的操作，在主程序中处理按键可以减少串口中断占用的处理器时间。当键盘事件
* 需要立刻处理，或者程序为中断驱动型，不存在主循环时，可以设置此回调函数，此函数会在检测到有效
* 键盘事件后自动呼叫。
* 此函数应该具有一个 unsigned char 类型的参数用以接收键值，而返回类型应该为 void. 
* 如果不设置或者设置为 NULL ,则回调函数不会被呼叫。请注意如果 update_key_status() 函数
* 是从串口中断中呼叫的，则此回调函数占用的将是中断的处理时间，与串口中断同级或优先级更低的中断
* 在返回前也不会得到执行，因此回调函数执行不宜占用过长的时间。
* 参数: pCallbackFunc  --  回调函数的函数指针
 ******************************************************************************/
void set_callback(void (*pCallbackFunc)(unsigned char))
{
    Keyboard.pCallback = pCallbackFunc;
}

/******************************************************************************
* 更新键盘状态
* 输入参数为UART所接收数据，此函数一般可在UART中断处理中呼叫，但也可以根据需要从任何地方呼叫。
* 参数: RxData  --  UART从BC6xxx或BC759x所接收到的数据
******************************************************************************/
void update_key_status(unsigned char RxData)
{
    unsigned char        i, j;
    unsigned char        PreviousCBKeyStat;
    const unsigned char* pCurrentKeySet;
    unsigned char        NumOfKeys;

    Keyboard.LastKeyEvent = RxData;        // 保存最新按键事件
    Keyboard.TimeCounter  = 0;
    if (Keyboard.DetectKeyRelease || !(Keyboard.LastKeyEvent & 0x80))        // 如果检测按键释放，或为按键按下，报告新按键
    {
        Keyboard.ReportedKey     = Keyboard.LastKeyEvent;
        Keyboard.NewKeyAvailable = 1;
    }
    if (Keyboard.CombinedKeyCount != 0)        // 设置了组合键，检查按键是否组合键一部分
    {
        for (i = 0; i < Keyboard.CombinedKeyCount; i++)        // 轮询所有组合键
        {
            PreviousCBKeyStat = *(Keyboard.pCombinedKeyStatusArray + i);
            pCurrentKeySet    = *(Keyboard.pCombinedKeys + i);
            NumOfKeys         = *(pCurrentKeySet);
            for (j = 0; j < NumOfKeys; j++)        // 轮询各组合键定义中各键值
            {
                if ((Keyboard.LastKeyEvent & 0x7f) == *(pCurrentKeySet + 2 + j))        // 产生事件的按键是否属于组合键成员
                {
                    if (Keyboard.LastKeyEvent & 0x80)        // 如果是按键释放
                    {
                        *(Keyboard.pCombinedKeyStatusArray + i) &= ~(1 << j);        // 相应标志位置0
                        if (PreviousCBKeyStat == FullBits[NumOfKeys])                // 如果设置为报告按键释放且组合键之前状态是全部按下
                        {
                            Keyboard.LastKeyEvent = (*(pCurrentKeySet + 1)) | 0x80;        // 组合键条件满足，更新键盘事件为设定的组合键键值
                            if (Keyboard.DetectKeyRelease)                                 // 如果设定为检测按键释放
                            {
                                Keyboard.ReportedKey     = Keyboard.LastKeyEvent;        // 设置键值为该组合键释放
                                Keyboard.NewKeyAvailable = 1;                            // 设置新按键标志
                            }
                        }
                    }
                    else        // 如果是按键按下
                    {
                        *(Keyboard.pCombinedKeyStatusArray + i) |= (1 << j);                       // 相应标志位置1
                        if (*(Keyboard.pCombinedKeyStatusArray + i) == FullBits[NumOfKeys])        // 如果组合键所有键均已按下
                        {
                            Keyboard.LastKeyEvent    = *(pCurrentKeySet + 1);        //	组合键条件满足，更新键盘事件为设定的组合键键值
                            Keyboard.ReportedKey     = Keyboard.LastKeyEvent;        //	设置键值为该组合键按下
                            Keyboard.NewKeyAvailable = 1;                            //	设置新按键标志
                        }
                    }
                    break;        // 跳出本组合键查询，继续查询此按键是否同时属于下一组合键组合
                }
            }
        }
    }
    if (Keyboard.NewKeyAvailable && (Keyboard.pCallback != NULL))
    {
        Keyboard.pCallback(Keyboard.ReportedKey);
        Keyboard.NewKeyAvailable = 0;
    }
}

/******************************************************************************
* 长按键计数
* 当希望进行长按键检测时，定期呼叫此函数，每呼叫此函数一次，则长按键计数加一。长按键的时间，
* 为用户通过 set_longpress_count() 设置的长按键计数目标值乘以呼叫此函数的时间间隔。此函数
* 一般在定时器中断中呼叫，但也可以根据需要从主程序循环或任何其它地方呼叫。
******************************************************************************/
void long_press_tick(void)
{
    unsigned char i;
    Keyboard.TimeCounter++;
    if (Keyboard.TimeCounter > Keyboard.LongPressCount)        // 如果长按键计时超时
    {
        Keyboard.TimeCounter = 0;
        if (Keyboard.pLongPressKeys != NULL)
        {
            for (i = 0; i < Keyboard.LongPressKeyCount; i++)        // 轮询各需要检测的长按键
            {
                if ((Keyboard.LastKeyEvent == *(*(Keyboard.pLongPressKeys + i)))
                    || ((Keyboard.LastKeyEvent & 0x80) && (*(*(Keyboard.pLongPressKeys + i)) == 0xFF)))        // 如果最后的按键活动是需要检测长按键的键值之一
                {
                    Keyboard.ReportedKey     = *(*(Keyboard.pLongPressKeys + i) + 1);        // 设置键值为用户定义的长按键键值
                    Keyboard.NewKeyAvailable = 1;
                    if (Keyboard.pCallback != NULL)
                    {
                        Keyboard.pCallback(Keyboard.ReportedKey);
                        Keyboard.NewKeyAvailable = 0;
                    }
                    break;
                }
            }
        }
    }
}

/******************************************************************************
* 获取当前键值
* 可在任何时候呼叫。呼叫此函数后，如果当前 is_key_changed() 函数返回非零,呼叫后则返回值会变成
* 零。仅返回当前工作模式下有效的键值，即如果设置为不检测按键释放，则仅会返回按键按下所对应的键值。
* 返回值: 键值。如果当前按键是组合键或长按键，则返回用户给组合键和长按键自定义的键值。
******************************************************************************/
unsigned char get_key_value(void)
{
    Keyboard.NewKeyAvailable = 0;
    return Keyboard.ReportedKey;
}

/******************************************************************************
* 定义组合键 def_combined_key()
* 此函数通知驱动库组合键的定义及组合键的用户自定义键值。
* 参数: pCBKeyList      --  组合键列表，为一数组，数组元素为指向各个组合键定义数组的指针
*      pCBKeyMap       --  为一unsigned char类型的数组，数组元素的数量必须大于或等于
*                          所定义组合键的个数
*      CBKeyCount      --  组合键的数量
*
* 欲使用组合键功能，用户需在程序中提供组合键的定义，并通过此函数提交给驱动库。
* 组合键定义数组，数据类型为 unsigned char ，每个数组长度由包含组合键数量决定，格式为
* {count, defined_value, key1, key2 ... }. 其中，count为本组合键所包含的按键数量，
* defined_value为用户自定义的代表组合键的键值，key1, key2等为各键的原始键值。
******************************************************************************/
void def_combined_key(const unsigned char** pCBKeyList, unsigned char* pCBKeyMap, unsigned char CBKeyCount)
{
    unsigned char i;
    Keyboard.pCombinedKeys           = pCBKeyList;
    Keyboard.CombinedKeyCount        = CBKeyCount & 0x07;
    Keyboard.pCombinedKeyStatusArray = pCBKeyMap;        // 申请存储空间, 每个组合键分配一个字节，每位代表一个键，故组合键最多可由8个键组成
    for (i = 0; i < CBKeyCount; i++)
    {
        *(Keyboard.pCombinedKeyStatusArray + i) = 0;        // 各键盘状态初始化为0(释放状态)
    }
}

/******************************************************************************
* 定义长按键 def_longpress_key()
* 此函数通知驱动库长按键的定义及用户所设置的对应长按键的键值。
* 参数: pLPKeyList      --  长按键列表，为一数组，数组元素为指向长按键定义数组的指针。
*      LPKeyCount      --  长按键的数量
*
* 长按键定义数组，数据类型为 unsigned char ,每个数组由2个元素组成，格式为 
* {key_value, defined_value} 第一个为待检测长按键的原始键值，此键值也可以是芯片
* 原生键值，也可以是用户自定义的组合键的键值，可以是按键按下对应键值，也可以是按键释放对应键值；
* 第二个参数为用户定义的代表该长按键的键值。
******************************************************************************/
void def_longpress_key(const unsigned char** pLPKeyList, unsigned char LPKeyCount)
{
    Keyboard.pLongPressKeys    = pLPKeyList;
    Keyboard.LongPressKeyCount = LPKeyCount;
}
